/*
Copyright (C) 2006 Ryan Mulder

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

/**
@file
@author Ryan Mulder
@author Steve Pierce
@date 3/13/06
Usage:
	The first command line argument is the path to the file to edit  (typically a resource file or header).
	If only the first argument is supplied, the "FILEVERSION" field is incremented
	If additional arguments are supplied, they are the string to search for before the version number. Example:  "#define AutoBuildNumber"

    Valid version numbers are a list of numbers separated by any one of:
	     ,.|\\/&*^%$#@+-_=`~?<>:
    However, only 1 of these separators can be used at once.
	Version numbers can be contained inside double quotes or not, and only the last value will be incremented by this app.

	Example: #define AutoBuildNumber 1.0.0.1
	Example: #define AutoBuildNumber "1,0,0,1"
*/

#include <wx/wx.h>

#ifdef _MSC_VER
#include <msvc/wx/setup.h>
#endif

#include <wx/app.h>
#include <wx/filename.h>
#include <wx/cmdline.h>
#include <wx/ffile.h>
#include <wx/tokenzr.h>

#define ABN_VERSION wxT("1.1.0.0")

static const wxCmdLineEntryDesc cmdLineDesc[] =
{
	{ wxCMD_LINE_SWITCH, wxT("h"), wxT("help"), wxT("Show this help message."), wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
	{ wxCMD_LINE_SWITCH, wxT("l"), wxT("locale"), wxT("Force using locale encoding, without trying UTF-8."), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
	{ wxCMD_LINE_SWITCH, wxT("v"), wxT("version"), wxT("Report the version of this program."), wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
	{ wxCMD_LINE_PARAM,  NULL, NULL, wxT("Filepath"),	wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY },
	{ wxCMD_LINE_PARAM,  NULL, NULL, wxT("Prefixes"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL},
	{ wxCMD_LINE_NONE }
};

void IncrementVersion( const wxString& linePrefix, wxString* fileContents, const wxString& delimiters = wxT(",") );


int main( int argc, char* argv[] )
{
	wxInitializer initializer;
	if ( !initializer )
	{
		fprintf( stderr, "Failed to initialize the wxWidgets library, aborting." );
		return 1;
	}

	// No timestamps on log messages
	wxLog::GetActiveTarget()->SetTimestamp( NULL );

	wxCmdLineParser parser( cmdLineDesc, argc, argv );
	parser.SetLogo(
	wxT("\nAutoBuildNumber v") ABN_VERSION wxT("\n\n")
	wxT("The first command line argument is the path to the file to edit  (typically a resource file or header).\n" )
	wxT("This file is expected to be encoded in UTF-8. If it is not, the locale encoding will be attempted.\n\n" )
	wxT("If only the first argument is supplied, the \"FILEVERSION\" field is incremented.\n")
	wxT("If additional arguments are supplied, they are the string to search for before the version number. Example:  \"#define AutoBuildNumber\"\n\n")

    wxT("Valid version numbers are a list of numbers separated by any one of:\n")
	wxT("\t,.|\\/&*^%$#@+-_=`~?<>:\n")
    wxT("However, only 1 of these separators can be used at once.\n")
	wxT("Version numbers can be contained inside double quotes or not, and only the last value will be incremented by this app.\n\n")

	wxT("Example: #define AutoBuildNumber 1.0.0.1\n")
	wxT("Example: #define AutoBuildNumber \"1,0,0,1\"\n")
	);

	switch ( parser.Parse( false ) )
	{
	case -1:
		// Help option found
		if ( parser.Found( wxT("version") ) )
		{
			wxLogMessage( wxT("AutoBuildNumber version ") ABN_VERSION );
		}
		else
		{
			parser.Usage();
		}
		return 0;

	case 0:
		// parsed successfully
		break;

	default:
		// syntax error
		parser.Usage();
		return 1;
	}	

	wxString path = parser.GetParam( 0 );

	wxLogMessage( wxT("Beginning AutoBuildNumber on file '%s'..."), path.c_str() );

	wxArrayString prefixes;
	size_t count = parser.GetParamCount();
	if ( count > 1 )
	{
		for ( size_t i = 1; i < count; ++i )
		{
			prefixes.Add( parser.GetParam( i ) );
		}
	}

	bool useLocale = parser.Found( wxT("locale") );

	try
	{
		if ( !wxFileName::FileExists( path ) )
		{
			wxLogError( wxT("The file does not exist.") );
			wxLogError( wxT("AutoBuildNumber failed.") );
			return 2;
		}

		// Read entire file into string
		wxFFile file( path.c_str() );
		if ( !file.IsOpened() )
		{
			wxLogError( wxT("The file could not be opened for reading.") );
			wxLogError( wxT("AutoBuildNumber failed.") );
			return 3;
		}

		wxString fileStr;
		bool readSuccessful = false;
		if ( useLocale )
		{
			readSuccessful = file.ReadAll( &fileStr, *wxConvCurrent );
		}
		else
		{
			readSuccessful = file.ReadAll( &fileStr, wxConvUTF8 );
		}

		if ( !readSuccessful )
		{
			wxLogError( wxT("%s Read failed."), ( useLocale ? wxT("Locale") : wxT("UTF8") ) );
			wxLogError( wxT("AutoBuildNumber failed.") );
			return 4;
		}

		if ( fileStr.empty() )
		{
			if ( useLocale )
			{
				// File was forced to be read with locale encoding, and it failed
				wxLogError( wxT("File is empty, or not locale encoding.") );
				wxLogError( wxT("AutoBuildNumber failed.") );
				return 5;
			}
			else
			{
				// File was read with UTF8 encoding, it failed, so try locale
				wxLogWarning( wxT("File is empty or is not UTF-8, attempting to use Locale encoding. (This can be forced with the -l switch)") );
				
				// Seek to start of file - the last ReadAll left the file pointer at the end of the file
				if ( !file.Seek( 0 ) )
				{
					wxLogError( wxT("Locale Seek(0) failed.") );
					wxLogError( wxT("AutoBuildNumber failed.") );
					return 6;
				}
				
				if ( !file.ReadAll( &fileStr, *wxConvCurrent ) )
				{
					wxLogError( wxT("Locale Read failed.") );
					wxLogError( wxT("AutoBuildNumber failed.") );
					return 7;
				}

				if ( fileStr.empty() )
				{
					// File is still empty
					wxLogError( wxT("File is empty, or encoding could not be determined.") );
					wxLogError( wxT("AutoBuildNumber failed.") );
					return 8;
				}
			}
		}

		if ( !file.Close() )
		{
			wxLogError( wxT("File '%s' could not be closed after reading."), path.c_str() );
			wxLogError( wxT("AutoBuildNumber failed.") );
			return 9;
		}

		// Increment the build number
		wxString delimeters = wxT(",.|\\/&*^%$#@+-_=`~?<>:");
		if ( prefixes.size() == 0 )
		{
			IncrementVersion( wxT("FILEVERSION"), &fileStr, delimeters );
		}
		else
		{
			for( size_t prefix = 0; prefix < prefixes.GetCount(); ++prefix )
			{
				IncrementVersion( prefixes[ prefix ], &fileStr, delimeters );
			}
		}

		// Backup file
		if ( !::wxCopyFile( path, path + wxT(".bak"), true ) )
		{
			wxLogError( wxT("File '%s' could not be backed-up to '%s.bak'."), path.c_str(), path.c_str() );
			wxLogError( wxT("AutoBuildNumber failed.") );
			return 10;
		}

		if ( !file.Open( path.c_str(), wxT("w") ) )
		{
			wxLogError( wxT("File '%s' could not be opened for writing.\nEnsure it is not Read-Only\nEnsure it is checked out, if necessary\n"), path.c_str() );
			wxLogError( wxT("AutoBuildNumber failed.") );
			return 11;
		}

		if ( !file.Write( fileStr, wxConvUTF8 ) )
		{
			wxLogError( wxT("File '%s' could not be written."), path.c_str() );
			wxLogError( wxT("AutoBuildNumber failed.") );
			return 12;
		}

		if ( !file.Close() )
		{
			wxLogError( wxT("File '%s' could not be closed after writing."), path.c_str() );
			wxLogError( wxT("AutoBuildNumber failed.") );
			return 13;
		}

		wxLogMessage( wxT("AutoBuildNumber completed successfully.") );

		return 0;

	}
	catch( int& e )
	{
		wxLogError( wxT("AutoBuildNumber Failed.") );
		return e;
	}

	return 0;
}

void IncrementVersion( const wxString& linePrefix, wxString* fileContents, const wxString& delimiters )
{

	// A line looks like:
	//		lineprefix "5,4,3,2"
	// where lineprefix is something like "#define VersionString", the quotation marks around the version number are optional,
	// and 2 is the build number to be incremented

	// Find the start of the line in the file
	size_t rc;
	rc = fileContents->find( linePrefix );

	if( rc != fileContents->npos )
	{
		// Get the entire line
		size_t len = fileContents->find_first_of( wxT("\r\n"), rc ) - rc;
		wxString line = fileContents->substr( rc, len );

		// Get the start and length of the version number
		size_t first = rc + linePrefix.length();
		len = fileContents->find_first_of( wxT("\r\n"), rc ) - first;

		// Save the old version string so it can be used during a replacement later
		wxString oldValStr = fileContents->substr( first, len );

		// Strip off quotation marks and extra whitespace
		wxString valStr = oldValStr;
		valStr.Replace( wxT("\""), wxT(" "), true );
		size_t lastNum = valStr.find_last_of( wxT("0123456789") );
		if ( lastNum == valStr.npos )
		{
			wxLogError( wxT("No numbers found.") );
			throw 100;
		}
		valStr.Truncate( lastNum + 1 );
		valStr.Trim( true );
		valStr.Trim( false );

		// Get the version numbers
		wxChar delimiter[2];
		delimiter[1] = wxT('\0');
		size_t delPos = valStr.find_first_of( delimiters.c_str() );
		if ( valStr.npos == delPos )
		{
			wxLogError( wxT("No delimiters found.") );
			throw 101;
		}

		// Determine delimiter
		delimiter[0] = valStr.at( delPos );

		// Get build number
		wxStringTokenizer toker( valStr, delimiter, wxTOKEN_STRTOK );
		wxString val = wxEmptyString;
		while ( toker.HasMoreTokens() )
		{
			val = toker.GetNextToken();
		}

		if ( val.empty() )
		{
			wxLogError( wxT("No values found") );
			throw 102;
		}

		// Increment the build number
		long buildNum;
		val.ToLong( &buildNum );
		buildNum++;

		// Build a new version number string
		wxString newValStr = valStr;
		size_t valLength = val.length();
		newValStr.Remove( toker.GetPosition() - valLength, valLength );
		newValStr.insert( toker.GetPosition() - valLength, wxString::Format( wxT("%i"), buildNum ) );

		// Build the replacement line
		oldValStr.Replace( valStr.c_str(), newValStr.c_str() );
		wxString newLine = linePrefix + oldValStr;

		// Replace the old line with the new line
		fileContents->Replace( line.c_str(), newLine.c_str() );
		wxLogMessage( wxT("\t%s"), newLine.c_str() );
	}
}

